/*
//   Copyright 2008 Dear Soft(http://dear-soft.com)
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
 */

package com.dear.soft.components.properties;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

import com.dear.soft.components.properties.renderers.ReferenceExpander;
import com.dear.soft.components.properties.renderers.ReferenceName;
import com.dear.soft.components.properties.renderers.ReferenceValue;

public class PropertiesTableModel implements TableModel {
	private final Set<TableModelListener> listeners;
	private final List<Category> categories;
	private final Set<PropertyDescriptor> properties;
	protected PropertyDescriptor[] sorted;
	private boolean flatMode;

	public PropertiesTableModel() {
		super();
		listeners = Collections
				.synchronizedSet(new HashSet<TableModelListener>());
		categories = Collections.synchronizedList(new ArrayList<Category>());
		properties = Collections
				.synchronizedSet(new TreeSet<PropertyDescriptor>());
	}

	public PropertiesTableModel(List<Category> categories) {
		this();
		for (Category c : categories) {
			addCategory(c);
		}
	}

	@Override
	public Class<?> getColumnClass(int columnIndex) {
		switch (columnIndex) {
		case 0:
			return ReferenceExpander.class;
		case 1:
			return ReferenceName.class;
		case 2:
			return ReferenceValue.class;
		}
		return String.class;
	}

	@Override
	public int getColumnCount() {
		return 3;
	}

	@Override
	public String getColumnName(int columnIndex) {
		return "";
	}

	public void addCategory(Category category) {
		categories.add(category);
		for (PropertyDescriptor p : category.getItems()) {
			properties.add(p);
		}
		List<PropertyDescriptor> nameSoted = new ArrayList<PropertyDescriptor>(
				properties);
		Collections.sort(nameSoted, new Comparator<PropertyDescriptor>() {
			public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
				int res = o1.getName().toLowerCase().compareTo(
						o2.getName().toLowerCase());
				if (res == 0) {
					res = o1.compareTo(o2);
				}
				return res;
			}
		});
		sorted = nameSoted.toArray(new PropertyDescriptor[] {});

		notifyTableChanged(null);
	}

	public void removeCategory(Category category) {
		categories.remove(category);
		for (PropertyDescriptor p : category.getItems()) {
			properties.remove(p);
		}
		sorted = properties.toArray(new PropertyDescriptor[] {});
		notifyTableChanged(null);
	}

	@Override
	public int getRowCount() {
		if (flatMode) {
			return properties.size();
		}
		int res = 0;
		for (Category c : categories) {
			res++;
			if (c.isExpanded()) {
				res += c.getSize();
			}
		}
		return res;
	}

	@Override
	public Object getValueAt(int rowIndex, int columnIndex) {
		if (flatMode && rowIndex < properties.size()) {
			return getValue(sorted[rowIndex], columnIndex);
		}

		int index = 0;
		for (Category c : categories) {
			if (index == rowIndex) {
				return getValue(c, columnIndex);
			}
			index++;
			if (c.isExpanded() && (index + c.getSize()) > rowIndex) {
				for (PropertyDescriptor p : c) {
					if (index == rowIndex) {
						return getValue(p, columnIndex);
					}
					index++;
				}
			} else {
				if (c.isExpanded()) {
					index += c.getSize();
				}
			}

		}
		return null;
	}

	private Object getValue(Category c, int index) {
		switch (index) {
		case 0:
			return c.getExpanderRef();
		case 1:
			return c.getNameRef();
		case 2:
			return c.getValueRef();
		}
		return null;
	}

	private Object getValue(PropertyDescriptor p, int index) {
		switch (index) {
		case 0:
			return p.getExpanderRef();
		case 1:
			return p.getNameRef();
		case 2:
			return p.getValueRef();
		}
		return null;
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return false;
	}

	@Override
	public void removeTableModelListener(TableModelListener l) {
		listeners.remove(l);
	}

	@Override
	public void addTableModelListener(TableModelListener l) {
		listeners.add(l);
	}

	protected void notifyTableChanged(TableModelEvent e) {
		List<TableModelListener> copy = null;
		synchronized (listeners) {
			copy = new ArrayList<TableModelListener>(listeners);
		}
		for (TableModelListener l : copy) {
			l.tableChanged(e);
		}
	}

	@Override
	public void setValueAt(Object value, int rowIndex, int columnIndex) {
		// TODO Auto-generated method stub
	}

	public static final PropertiesTableModel demo() {
		Category first = new Category("Fruits", "Fruiits bucket");
		first.add(new PropertyDescriptor("Apple", "Big red apple", "RED"));
		first.add(new PropertyDescriptor("Orange", "Juicy orange", "ORANGE"));
		first.add(new PropertyDescriptor("Plum", "Big plum", "BLUE"));
		first.add(new PropertyDescriptor("Water Melon", "Summer water melon",
				"GREEN"));
		first.expand();
		Category second = new Category("Sport", "For funs");
		second.add(new PropertyDescriptor("Football", "Outdor sport", "BALL"));
		second.add(new PropertyDescriptor("Hockey", "Winter sport", "PUCK"));
		second.add(new PropertyDescriptor("Snooker", "Show sport", "CUE"));
		second.expand();
		PropertiesTableModel result = new PropertiesTableModel();
		result.addCategory(first);
		result.addCategory(second);
		return result;
	}

	public List<Category> getCategories() {
		return categories;
	}

	public void setFlatMode(boolean flatMode) {
		if (flatMode != this.flatMode) {
			this.flatMode = flatMode;
			notifyTableChanged(null);
		}
	}

}
